package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.pms.entity.BrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.atguigu.gmall.search.service.SearchService;
import com.atguigu.gmall.search.vo.Goods;
import com.atguigu.gmall.search.vo.QueryParamVo;
import com.atguigu.gmall.search.vo.SearchResponseAttrVo;
import com.atguigu.gmall.search.vo.SearchResponseVo;
import com.google.gson.Gson;
import io.jsonwebtoken.lang.Collections;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.stereotype.Service;

import javax.sound.midi.Soundbank;
import java.io.IOException;
import java.util.Date;
import java.util.List;
@Slf4j
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    RestHighLevelClient client;
    Gson gson = new Gson();
    @Override
    public SearchResponseVo search(QueryParamVo queryParamVo) {
        try {
            //SearchRequest用来封装请求报文
            SearchRequest request = new SearchRequest();
            request.indices("goods");//检索时使用的索引
            //构建dsl语句
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            //获取查询的条件设置给java es客户端 执行查询并解析结果
            //1、构建 匹配 过滤查询的DSL的构建器
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            //查询关键字
            String keyword = queryParamVo.getKeyword();
            if(StringUtils.isNotBlank(keyword)){
                //标题匹配查询
                boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword));
            }
            //品牌id集合
            List<Long> brandId = queryParamVo.getBrandId();
            if(!Collections.isEmpty(brandId)){
                //品牌id集合的过滤查询
                boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId" , brandId));
            }
            //分类id
            Long cid = queryParamVo.getCid();
            if(cid!=null){
                //分类id过滤查询
                boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", cid));
            }
            //价格区间
            Double priceFrom = queryParamVo.getPriceFrom();
            Double priceTo = queryParamVo.getPriceTo();
            if(priceFrom!=null || priceTo!=null){
                //价格区间的过滤查询
                RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
                if(priceFrom!=null){
                    rangeQuery.gte(priceFrom);
                }
                if(priceTo!=null){
                    rangeQuery.lte(priceTo);
                }
                boolQueryBuilder.filter(rangeQuery);
            }
            //是否有库存
            Boolean store = queryParamVo.getStore();
            if(store!=null){
                //库存过滤
                boolQueryBuilder.filter(QueryBuilders.termQuery("store",store));
            }
            //检索属性的过滤：嵌套类型的操作
            //4:8G-12G
            List<String> props = queryParamVo.getProps();
            if(!Collections.isEmpty(props)){
                props.forEach(prop->{
                    //解析prop字符串中的 attrId 和 attrValue集合
                    String[] split = prop.split(":");
                    if(split!=null && split.length == 2){
                        String attrId = split[0];
                        String[] attrValues = split[1].split("-");
                        //一个检索属性的嵌套查询使用布尔组合的方式处理
                        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                        boolQuery.must(QueryBuilders.termQuery("searchAttrs.attrId",attrId));
                        boolQuery.must(QueryBuilders.termsQuery("searchAttrs.attrValue",attrValues));
                        boolQueryBuilder.filter(QueryBuilders
                                .nestedQuery("searchAttrs" ,boolQuery  , ScoreMode.None));
                    }

                });
            }
            sourceBuilder.query(boolQueryBuilder);
            //分页、排序、高亮、结果字段筛序过滤
            //排序：
            //0默认排序     1价格升序  2价格降序  3上架时间降序  4销量排序
            Integer sort = queryParamVo.getSort();
            SortOrder order;
            String orderName;
            switch (sort){
                case 1:
                    orderName = "price"; order = SortOrder.ASC ;
                    break;
                case 2:  orderName = "price"; order = SortOrder.DESC ;
                    break;
                case 3:  orderName = "createTime"; order = SortOrder.DESC ;
                    break;
                case 4: orderName = "sales"; order = SortOrder.DESC;
                    break;
                default: orderName = "_score";  order = SortOrder.DESC; //默认使用_score的值 降序排列
            }
            //分页
            // (pageNum-1)*pageSize :   total 6, pageNum 1 , pageSize = 4
            sourceBuilder.from((queryParamVo.getPageNum()-1)*queryParamVo.getPageSize());//起始索引
            sourceBuilder.size(queryParamVo.getPageSize());//查询的记录条数
            //高亮
            sourceBuilder.highlighter(new HighlightBuilder().field("title")
                                .preTags("<font style='color:red;'>")
                                .postTags("</font>"));

            sourceBuilder.sort(orderName, order);
            //结果字段的过滤：将页面中不需要展示的数据过滤掉
            sourceBuilder.fetchSource(new String[]{
                    "skuId","defaultImage","price","title","subtitle"
            } , null);

            //聚合
            //分类聚合
            sourceBuilder.aggregation(AggregationBuilders.terms("categoryIdAgg")
                                    .field("categoryId")
                                    .subAggregation(AggregationBuilders.terms("categoryNameAgg")
                                                    .field("categoryName")));
            //品牌聚合
            sourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId")
                                        .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName"))
                                        .subAggregation(AggregationBuilders.terms("logoAgg").field("logo")));
            //属性和属性值聚合： 嵌套属性聚合的使用
            sourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","searchAttrs")
                    .subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrs.attrId")
                                    .subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrs.attrName"))
                                    .subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrs.attrValue"))));
            log.info("{} 生成的DSL：{}",new Date() , sourceBuilder.toString());
            request.source(sourceBuilder);
            //客户端对象向es发起请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //===================解析查询的结果
            //数据模型存放响应结果：
            // List<Goods> goods;  存放响应的数据列表
            // List<BrandEntity>brandEntities; 存放 品牌集合
            //List<CategoryEntiry> cates; 存放分类集合
            // List<SearchVo> svs;  存放 检索属性和值对象
            //SearchVo:  attrId  attrName  List<String> attrValues;
            SearchResponseVo searchResponseVo = new SearchResponseVo();
            //查询的goods数据
            SearchHit[] hits = response.getHits().getHits();
            if(ArrayUtils.isNotEmpty(hits)){
                for (SearchHit hit : hits) {
                    Goods goods = gson.fromJson(hit.getSourceAsString(), Goods.class);
                    //给goods设置高亮标题
                    HighlightField highlightField = hit.getHighlightFields().get("title");
                    if(highlightField!=null && ArrayUtils.isNotEmpty(highlightField.getFragments()) ){
                        String highlightTitle = StringUtils.join(highlightField.getFragments(), "");
                        goods.setTitle(highlightTitle);
                    }
                    searchResponseVo.getGoodsList().add(goods);
                }
            }
            //分页属性值
            searchResponseVo.setPageNum(queryParamVo.getPageNum());
            searchResponseVo.setPageSize(queryParamVo.getPageSize());
            searchResponseVo.setTotal(response.getHits().getTotalHits().value);//总记录条数
            //聚合的结果
            //如果聚合时使用的属性值是字符串类型  返回的聚合类型是ParsedStringTerms
            // 如果聚合时使用的属性值是Long类型 返回的聚合类型是ParsedLongTerms
//            1、分类聚合
            ParsedLongTerms categoryIdAgg = response.getAggregations().get("categoryIdAgg");
            List<? extends Terms.Bucket> categoryIdAggBuckets = categoryIdAgg.getBuckets();
            if(!Collections.isEmpty(categoryIdAggBuckets)){
                categoryIdAggBuckets.forEach(categoryIdAggBucket->{
                    String categoryId = categoryIdAggBucket.getKey().toString();
                    //获取当前id分类下的子桶：分类名称  分类id和名称是1:1
                    ParsedStringTerms categoryNameAgg = categoryIdAggBucket.getAggregations().get("categoryNameAgg");
                    String categoryName = categoryNameAgg.getBuckets().get(0).getKey().toString();
                    CategoryEntity categoryEntity = new CategoryEntity();
                    categoryEntity.setId(Long.parseLong(categoryId));
                    categoryEntity.setName(categoryName);
                    searchResponseVo.getCategories().add(categoryEntity);
                });
            }
            //2、品牌聚合
            ParsedLongTerms brandIdAgg = response.getAggregations().get("brandIdAgg");
            List<? extends Terms.Bucket> brandIdAggBuckets = brandIdAgg.getBuckets();
            if(!Collections.isEmpty(brandIdAggBuckets)){
                brandIdAggBuckets.forEach(brandIdAggBucket->{
                    //获取brandId
                    String bId = brandIdAggBucket.getKey().toString();
                    ParsedStringTerms brandNameAgg =  brandIdAggBucket.getAggregations().get("brandNameAgg");
                    ParsedStringTerms logoAgg = brandIdAggBucket.getAggregations().get("logoAgg");
                    String bName = brandNameAgg.getBuckets().get(0).getKey().toString();
                    String bLogo = logoAgg.getBuckets().get(0).getKey().toString();
                    BrandEntity brandEntity = new BrandEntity();
                    brandEntity.setLogo(bLogo);
                    brandEntity.setName(bName);
                    brandEntity.setId(bId);
                    searchResponseVo.getBrands().add(brandEntity);
                });


            }

            //3、属性和值的解析: 嵌套属性的聚合的解析
            ParsedNested attrAgg = response.getAggregations().get("attrAgg");
            ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
            //attrIdAgg聚合的每个桶 代表一个检索属性组： attrId  attrName  List<AttrValue>
            List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
            if(!Collections.isEmpty(attrIdAggBuckets)){
                attrIdAggBuckets.forEach(attrIdAggBucket->{
                    SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
                    String attrId = attrIdAggBucket.getKey().toString();
                    attrVo.setAttrId(Long.parseLong(attrId));
                    //获取桶内的子聚合： attrName  attrValue
                    ParsedStringTerms attrNameAgg = attrIdAggBucket.getAggregations().get("attrNameAgg");
                    String attrName = attrNameAgg.getBuckets().get(0).getKey().toString();
                    attrVo.setAttrName(attrName);
                    ParsedStringTerms attrValueAgg = attrIdAggBucket.getAggregations().get("attrValueAgg");
                    List<? extends Terms.Bucket> attrValueAggBuckets = attrValueAgg.getBuckets();
                    if(!Collections.isEmpty(attrValueAggBuckets)){
                        attrValueAggBuckets.forEach(attrValueAggBucket->{
                            String attrValue = attrValueAggBucket.getKey().toString();
                            attrVo.getAttrValues().add(attrValue);
                        });
                    }
                    searchResponseVo.getFilters().add(attrVo);
                });
            }
//            System.out.println(searchResponseVo);
            return searchResponseVo;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
